﻿//using Nemerle.Collections;
using Nemerle.Text;
using Nemerle.Utility;

using Nemerle.Data;
using Nemerle.Data.NList;

using System;
using System.Collections.Generic;
using System.Linq;

using FsCheck;
using FsCheck.Fluent;

namespace QCTests
{
  public module TestNLists
  {  
    public Run() : void
    {
      _ = DefaultArbitraries.Add.[NArbitraries]();
      
      Spec.ForAny(xs : list[bool] => xs.And() == xs.All(x => x))
        .QuickCheck("And");
        //.VerboseCheck("And");
        
      //Spec.ForAny((xs : list[int], x : int) => xs.Append(x).SequenceEqual(xs.Concat(array[x])))
      //  .QuickCheck("Append");
        
      Spec.ForAny((xs : list[int], x : int) => { 
        def (ys, zs) = xs.Break(_ <= x);
        def (ys', zs') = xs.ToArray().Break(_ <= x);
        ys.SequenceEqual(ys') && zs.SequenceEqual(zs')
        })
        .QuickCheck("Break");
        
      Spec.ForAny(xs : list[option[int]] => xs.CatOptions().SequenceEqual(xs.ToArray().CatOptions()))
        .QuickCheck("CatOptions");
        //.VerboseCheck("CatOptions");
        
      Spec.ForAny(xss : list[list[int]] => xss.Concatenate().SequenceEqual(xss.SelectMany(xs => xs)))
        .QuickCheck("Concatenate");
        
      //Spec.ForAny((xs : array[int], ys : array[int]) => xs.Concatenate(ys).SequenceEqual(xs.Concat(ys)))
      //  .QuickCheck("Concatenate2");
        
      Spec.ForAny(xs : list[list[int]] => xs.ConcatMap(x => [x, x]).SequenceEqual(xs.SelectMany(x => [x, x])))
        .QuickCheck("ConcatMap");
        
      Spec.ForAny((xs : list[int], x : int) => xs.ContainsElem(x) == xs.Any(_ == x))
        .QuickCheck("ContainsElem");
        
      //Spec.ForAny(xs : array[int] => xs.Copy().SequenceEqual(xs))
      //  .QuickCheck("Copy");
        
      Spec.ForAny((xs : list[int], n : int) => xs.DeleteFirst(n).SequenceEqual(xs.ToArray().DeleteFirst(n)))
        .QuickCheck("DeleteFirst");
        
      Spec.ForAny((xs : list[int], n : int) => xs.DeleteFirstWhen(_ == n)
          .SequenceEqual(xs.ToArray().DeleteFirstWhen(_ == n)))
        .QuickCheck("DeleteFirstWhen");
      
      Spec.ForAny((xs : list[int], ys : list[int]) => xs.DeleteFirsts(ys)
          .SequenceEqual(xs.ToArray().DeleteFirsts(ys.ToArray())))
        .QuickCheck("DeleteFirsts");
        
      Spec.ForAny((xs : list[int], ys : list[int]) => xs.DeleteFirstsBy(ys, x => -x)
          .SequenceEqual(xs.ToArray().DeleteFirstsBy(ys.ToArray(), x => -x)))
        .QuickCheck("DeleteFirstsBy");
               
      Spec.ForAny((xs : list[int], ys : list[int]) => xs.Diff(ys)
          .SequenceEqual(xs.ToArray().Diff(ys.ToArray())))
        .QuickCheck("Diff");
        
      Spec.ForAny((xs : list[int], ys : list[int]) => xs.DiffBy(ys, x => -x)
          .SequenceEqual(xs.ToArray().DiffBy(ys.ToArray(), x => -x)))
        .QuickCheck("DiffBy");
        
      Spec.ForAny((xs : list[int], n : int) => xs.Drop(n).SequenceEqual(xs.Skip(n)))
        .QuickCheck("Drop");
      
      Spec.ForAny((xs : list[int], n : int) => xs.DropAtMost(n).SequenceEqual(xs.Skip(n)))
        .QuickCheck("DropAtMost");  
      
      Spec.ForAny((xs : list[int], n : int) => xs.DropExactly(n).SequenceEqual(xs.Skip(n)))
        .When((xs,n) => n <= xs.Length && n >= 0)
        .QuickCheck("DropExactly");
        
      //Spec.ForAny((xs : array[int], n : int) => xs.DropLasts(n).SequenceEqual(xs.Reverse().Skip(n).Reverse()))
      //  .QuickCheck("DropLasts");
        
      Spec.ForAny((xs : list[int], n : int) => xs.DropWhile(_ > n).SequenceEqual(xs.SkipWhile(_ > n)))
        .QuickCheck("DropWhile"); 
        
      Spec.ForAny((xs : list[int], x : int) => x.ElementOf(xs) == x.ElementOf(xs.ToArray()))
        .QuickCheck("ElementOf");
        
      //WriteLine(if (Arrays.Empty.[int]().SequenceEqual(array(0) : array[int])) "Empty-Ok, passed 1 test." else "Empty-Falsifiable, after 1 test.");
      
      Spec.ForAny(xs : list[int] => xs.Essence().SequenceEqual(xs.Distinct()))
        .QuickCheck("Essence");
        
      Spec.ForAny(xs : list[int] => xs.EssenceBy(_.ToString()).SequenceEqual(xs.Distinct(SelectEqComparer(_.ToString()))))
        .QuickCheck("EssenceBy");
      
      Spec.ForAny((xs : list[int], x : int) => xs.Exist(_ > x) == xs.Any(_ > x))
        .QuickCheck("Exist");
        
      Spec.ForAny((xs : list[int], x : int) => NLists.Filter(xs, _ > x).SequenceEqual(xs.Where(_ > x)))
        .QuickCheck("Filter");
        
      Spec.ForAny((xs : list[int], x : int) => NLists.FilterRev(xs, _ > x).Rev().SequenceEqual(xs.Where(_ > x)))
        .QuickCheck("FilterRev_1");
        
      Spec.ForAny((xs : list[int], x : int) => ReversedList(xs.Rev()).FilterRev(_ > x).SequenceEqual(xs.Where(_ > x)))
        .QuickCheck("FilterRev_2");
        
      Spec.ForAny(xs : list[string] => xs.FilterI((i, x) => x.Length < i).SequenceEqual(xs.Where((x, i) => x.Length < i)))
        .QuickCheck("FilterI");
        
      //def toOption(value, pred) {
      
      Spec.ForAny((xs : list[int], x : int) => Option.Equals(NLists.Find(xs, _ > x), xs.ToArray().Find(_ > x)))
        .QuickCheck("Find");
          
      //Spec.ForAny((xs : array[int], x : int) => Option.Equals(xs.FindIndex(_ > x), toOption(Array.FindIndex(xs, _ > x), _ > -1)))
      //  .QuickCheck("FindIndex");
      
      //Spec.ForAny((xs : array[int], x : int) => xs.FindIndices(_ > x).SequenceEqual(xs.Select((e, i) => (e, i)).Where((e, _) => e > x).Select(((_, i)) => i)))
      //  .QuickCheck("FindIndices");
        
      Spec.ForAny(xs : list[int*string] => xs.Firsts().SequenceEqual(xs.ToArray().Firsts()))
        .QuickCheck("Firsts");
        
      Spec.ForAny((xs : list[char], x : string) => xs.Fold(x, (s, c) => s + c.ToString()) == xs.Aggregate(x, (s, c) => s + c.ToString()))
        .QuickCheck("Fold");
        
      Spec.ForAny((xs : list[char], x : string) => xs.FoldI(x, (i, s, c) => s + c.ToString() + i.ToString()) == 
        xs.ToArray().FoldI(x, (i, s, c) => s + c.ToString() + i.ToString()))
        .QuickCheck("FoldI");
        
      Spec.ForAny((xs : list[char], x : string) => NLists.FoldRight(xs, x, (c, s) => s + c.ToString()) == 
        xs.ToArray().FoldRight(x, (c, s) => s + c.ToString()))
        .QuickCheck("FoldRight");
        
      Spec.ForAny((xs : list[char], x : string) => xs.FoldRightI(x, (i, c, s) => s + c.ToString() + i.ToString()) == 
        xs.ToArray().FoldRightI(x, (i, c, s) => s + c.ToString() + i.ToString()))
        .QuickCheck("FoldRightI");
        
      Spec.ForAny((xs : list[int], x : int) => NLists.ForAll(xs, _ > x) == xs.All(_ > x))
        .QuickCheck("ForAll");
        
      Spec.ForAny((xs : list[int], n : int) => xs.GetWhile(_ > n).SequenceEqual(xs.TakeWhile(_ > n)))
        .QuickCheck("GetWhile"); 
      
      //Spec.ForAny(xs : array[int] => xs.Head() == xs[0])
      //  .When(xs => xs.Length > 0)
      //  .QuickCheck("Head");
        
      //Spec.ForAny(xs : array[int] => if(xs.Length > 0) xs.HeadOrNone().Value == xs[0] else xs.HeadOrNone().IsNone)
      //  .QuickCheck("HeadOrNone");
        
      Spec.ForAny((xs : list[int], x : int) => Option.Equals(xs.IndexOf(x), xs.ToArray().IndexOf(x)))
        .QuickCheck("IndexOf");
      
      Spec.ForAny((xs : list[int], x : int) => xs.IndicesOf(x).SequenceEqual(xs.ToArray().IndicesOf(x)))
        .QuickCheck("IndicesOf");
        
      //Spec.ForAny(xs : list[int] => xs.Init().SequenceEqual(xs.Reverse().Skip(1).Reverse()))
      //  .QuickCheck("Init");
        
      Spec.ForAny(xs : list[int] => xs.Inits().MapI(x => x).ForAll((i, ys) => ys.SequenceEqual(xs.Take(i))))
        .QuickCheck("Inits");
        
      Spec.ForAny((xs : list[int], x : int) => xs.Sort().Insert(x).SequenceEqual(
        xs.ToArray().Sort().Insert(x)))
        .QuickCheck("Insert");
        
      Spec.ForAny((xs : list[int], x : int) => xs.InsertAt(x, 0).SequenceEqual(x :: xs))
        .When((xs, _) => xs.Length > 0)
        .QuickCheck("InsertAt_0_1"); 
        
      Spec.ForAny((xs : list[int], x : int) => xs.InsertAt(x, xs.Length).SequenceEqual(xs + [x]))
        .When((xs, _) => xs.Length > 0)
        .QuickCheck("InsertAt_0_2"); 
        
      Spec.ForAny((xs : list[int], n : int) => xs.InsertAt(-n, n).SequenceEqual(xs.ToArray().InsertAt(-n, n)))
        .QuickCheck("InsertAt"); 
        
      Spec.ForAny((xs : list[string], x : string) => xs.SortBy(_.Length).InsertBy(x, _.Length)
          .SequenceEqual(xs.ToArray().SortBy(_.Length).InsertBy(x, _.Length)))
        .QuickCheck("InsertBy");
        
      Spec.ForAny((xs : list[int], n : int) => xs.InsertExactlyAt(-n, n).SequenceEqual(xs.ToArray().InsertAt(-n, n)))
        .When((xs, n) => n <= xs.Length && n >= 0)
        .QuickCheck("InsertExactlyAt"); 
        
      //Spec.ForAny((xs : list[list[int]], x : list[int]) => xs.Intercalate(x)
      //  .SequenceEqual(string.Join(s, xs)))
      //  .QuickCheck("Intercalate");
        
      Spec.ForAny((xs : list[int], ys : list[int]) => xs.Intersection(ys).SequenceEqual(xs.Intersect(ys)))
        .QuickCheck("Intersection");
        
      Spec.ForAny((xs : list[int], ys : list[int]) => xs.IntersectionBy(ys, x => -x).SequenceEqual(xs.Intersect(ys, SelectEqComparer(x => -x))))
        .QuickCheck("IntersectionBy");
        
      Spec.ForAny((xs : list[int], x : int) => xs.Intersperse(x).SequenceEqual(xs.ToArray().Intersperse(x)))
        .QuickCheck("Intersperse");
        
      Spec.ForAny(xs : list[int] => xs.IntersperseWith(_-_)
        .SequenceEqual(xs.ToArray().IntersperseWith(_-_)))
        .When(xs => xs.Length > 1)
        .QuickCheck("IntersperseWith");
          
      //Spec.ForAny(xs : array[int] => { mutable summ = 0; xs.Iter(x => summ += x); summ == xs.Sum() })
      //  .QuickCheck("Iter");
      
      //Spec.ForAny(xs : array[string] => { def lst = List(); xs.IterI((i, x) => lst.Add(x + i.ToString())); lst.SequenceEqual(xs.Select((x, i) => x + i.ToString()))})
      //  .QuickCheck("IterI");
        
      //Spec.ForAny(xs : array[int] => xs.IsEmpty() == !xs.Any())
      //  .QuickCheck("IsEmpty");
        
      //Spec.ForAny(xs : array[int] => xs.IsNullOrEmpty() == !xs.Any())
      //  .QuickCheck("IsNullOrEmpty_1");
        
      //WriteLine(if ((null : array[int]).IsNullOrEmpty()) "IsNullOrEmpty_2-Ok, passed 1 test." else "IsNullOrEmpty_2-Falsifiable, after 1 test.");
      
      //Spec.ForAny(xs : array[int] => xs.LastElem() == xs.Last())
      //  .When(xs => xs.Length > 0)
      //  .QuickCheck("LastElem");
        
      Spec.ForAny(xs : list[int] => xs.Map(x => -x).SequenceEqual(xs.Select(x => -x)))
        .QuickCheck("Map");
        
      Spec.ForAny(xs : list[string] => { 
        def (acc, ys) = xs.MapAcc(0, (i, x) => (i + 2, i.ToString() + x));
        def (acc', ys') = xs.ToArray().MapAcc(0, (i, x) => (i + 2, i.ToString() + x));
        ys.SequenceEqual(ys') && acc == acc'
        })
        .QuickCheck("MapAcc");
        
      Spec.ForAny(xs : list[string] => { 
        def (acc, ys) = xs.MapAccRight(0, (i, x) => (i + 2, i.ToString() + x));
        def (acc', ys') = xs.ToArray().MapAccRight(0, (i, x) => (i + 2, i.ToString() + x));
        ys.SequenceEqual(ys') && acc == acc'
        })
        .QuickCheck("MapAccRight");
        
      def zipf(x, y) { x.ToString() + y };   
      
      Spec.ForAny((xs : list[int], ys : list[string]) => {
        def l = Math.Min(xs.Length, ys.Length);
        def xs' = xs.Take(l).ToNList();
        def ys' = ys.Take(l).ToNList();
        xs'.Map2(ys', zipf).SequenceEqual(xs'.ToArray().Map2(ys'.ToArray(), zipf))
      })
        .QuickCheck("Map2");
        
      Spec.ForAny(xs : list[string] => xs.MapI((i, x) => i.ToString() + x).SequenceEqual(xs.Select((x, i) => i.ToString() + x)))
        .QuickCheck("MapI");
        
      Spec.ForAny((xs : list[int], x : int) => xs.MapOption(y => if (y <= x) Some(y + 1) else None())
        .SequenceEqual(xs.Filter(_ <= x).Map(_ + 1)))
        .QuickCheck("MapOption");
        
      Spec.ForAny(xs : list[int] => xs.Maximum() == xs.Max())
        .When(xs => !xs.IsEmpty)
        .QuickCheck("Maximum");
        
      Spec.ForAny(xs : list[int] => xs.MaximumBy(x => x) == xs.Maximum())
        .When(xs => !xs.IsEmpty)
        .QuickCheck("MaximumBy_1");
        
      Spec.ForAny(xs : list[string] => xs.MaximumBy(_.Length).Length == xs.Max.[string, int](x => x.Length))
        .When(xs => !xs.IsEmpty)
        .QuickCheck("MaximumBy_2");
        
      Spec.ForAny(xs : list[int] => xs.MaximumsBy(x => x).Head() == xs.Maximum())
        .When(xs => !xs.IsEmpty)
        .QuickCheck("MaximumsBy_1");
        
      Spec.ForAny(xs : list[int] => xs.MaximumsBy(x => -x).SequenceEqual(xs.ToArray().MaximumsBy(x => -x)))
        .When(xs => !xs.IsEmpty)
        .QuickCheck("MaximumsBy_2");
        
      Spec.ForAny((x : int, n : int) => NLists.Replicate(x, n).MaximumsBy(y => y).Length == n)
        .When((_, n) => n > 0)
        .QuickCheck("MaximumsBy_3");
        
      Spec.ForAny(xs : list[int] => xs.Minimum() == xs.Min())
        .When(xs => !xs.IsEmpty)
        .QuickCheck("Minimum");
        
      Spec.ForAny(xs : list[int] => xs.MinimumBy(x => -x) == xs.ToArray().MinimumBy(x => -x))
        .When(xs => !xs.IsEmpty)
        .QuickCheck("MinimumBy");
        
      Spec.ForAny(xs : list[int] => xs.MinimumsBy(x => x).Head() == xs.Minimum())
        .When(xs => !xs.IsEmpty)
        .QuickCheck("MinimumsBy_1");

      Spec.ForAny(xs : list[int] => xs.MinimumsBy(x => -x).SequenceEqual(xs.ToArray().MinimumsBy(x => -x)))
        .When(xs => !xs.IsEmpty)
        .QuickCheck("MinimumsBy_2");
        
      Spec.ForAny((x : int, n : int) => NLists.Replicate(x, n).MinimumsBy(y => y).Length == n)
        .When((_, n) => n > 0)
        .QuickCheck("MinimumsBy_3");
        
      Spec.ForAny((xs : list[int], i : int) => NLists.Nth(xs, i).Value == xs.ToArray()[i])
        .When((xs, i) => i >= 0 && i < xs.Length)
        .QuickCheck("Nth_1");
        
      Spec.ForAny((xs : list[int], i : int) => NLists.Nth(xs, i).IsNone)
        .When((xs, i) => i < 0 || i >= xs.Length)
        .QuickCheck("Nth_2");
        
      Spec.ForAny(xs : list[bool] => xs.Or() == xs.Any(x => x))
        .QuickCheck("Or");
        
     Spec.ForAny(xs : list[int] => xs.Pairwise().SequenceEqual(xs.ZipSeq(xs.Tail())))
        .When(xs => xs.Length > 1)
        .QuickCheck("Pairwise_1");
        
      Spec.For(LimSeq.NList01(), xs : list[int] => xs.Pairwise().SequenceEqual([]))
        .QuickCheck("Pairwise_2");
        
      Spec.ForAny(xs : array[int] => xs.PairwiseWith(_ + _).SequenceEqual(xs.ZipWith(xs.Tail(), _ + _)))
        .When(xs => xs.Length > 1)
        .QuickCheck("PairwiseWith_1");
        
      Spec.For(LimSeq.NList01(), xs : list[int] => xs.PairwiseWith(_ + _).SequenceEqual([]))
        .QuickCheck("PairwiseWith_2");
        
      Spec.ForAny((xs : list[int], x : int) => {
          def (ts, fs) = xs.Partition(_ > x); 
          ts.SequenceEqual(xs.Filter(_ > x)) && fs.SequenceEqual(xs.Filter(_ <= x))
        })
        .QuickCheck("Partition");
        
      //Spec.ForAny((xs : array[int], x : int) => xs.Prepend(x).SequenceEqual(array[x].Concat(xs)))
      //  .QuickCheck("Prepend");
        
      Spec.ForAny(xs : list[string] => xs.Reduce(_ + _) == xs.Aggregate(_ + _))
        .When(xs => !xs.IsEmpty)
        .QuickCheck("Reduce");
                
      //Spec.ForAny(xs : list[int] => xs.ReducePairs(_ + _).SequenceEqual(xs.ToArray().ReducePairs(_ + _)))
      //  .When(xs => xs.Length % 2 == 0)
      //  .QuickCheck("ReducePairs");  
      
      Spec.ForAny(xs : list[string] => xs.ReduceRight(_ + _) == xs.ToArray().ReduceRight(_ + _))
        .When(xs => !xs.IsEmpty)
        .QuickCheck("ReduceRight");
        
      Spec.ForAny((x : string, n : int) => NLists.Replicate(x, n).SequenceEqual(Arrays.Replicate(x, n)))
        .QuickCheck("Replicate");
        
      Spec.ForAny((x : string, n : int) => NLists.ReplicateResult(() => x, n)
        .SequenceEqual(Arrays.ReplicateResult(() => x, n)))
        .QuickCheck("ReplicateResult");
        
      //Spec.ForAny(xs : array[int] => xs.Rev().Rev().SequenceEqual(xs))
      //  .QuickCheck("Rev");
        
      Spec.ForAny((xs : list[int], seed : string) => xs.Scan(seed, (s, x) => s + x.ToString()) 
        .SequenceEqual(xs.ToArray().Scan(seed, (s, x) => s + x.ToString())))
        .QuickCheck("Scan");

      Spec.ForAny((xs : list[int], seed : string) => xs.ScanRight(seed, (x, s) => s + x.ToString()) 
        .SequenceEqual(xs.ToArray().ScanRight(seed, (x, s) => s + x.ToString())))
        .QuickCheck("ScanRight");
              
      //Spec.ForAny((xs : array[char], x : string) => xs.SCFold(x, (s, c) => Some(s + c.ToString())) == xs.Aggregate(x, (s, c) => s + c.ToString()))
      //  .QuickCheck("SCFold_1");
        
      //Spec.ForAny(xs : array[bool] => {
      //    mutable i = 0;
      //    _ = xs.SCFold(true, (s, e) => if(e) {i++; Some(s && e)} else None());
      //    def res = xs.FindIndex(x => !x);
      //    if (res.IsSome) res.Value == i else true
      //  })
      //  .QuickCheck("SCFold_2");
        
      Spec.ForAny(xs : list[int*string] => xs.Seconds().SequenceEqual(xs.ToArray().Seconds()))
        .QuickCheck("Seconds");
        
      Spec.ForAny((xs : list[int], ys : list[int]) => xs.SetAdd(ys).SequenceEqual(xs.Union(ys)))
        .QuickCheck("SetAdd");
        
      Spec.ForAny((xs : list[int], ys : list[int]) => xs.SetAddBy(ys, x => -x).SequenceEqual(xs.Union(ys, SelectEqComparer(x => -x))))
        .QuickCheck("SetAddBy");
        
      Spec.ForAny((xs : list[int], ys : list[int]) => xs.SetDiff(ys).SequenceEqual(xs.Except(ys)))
        .QuickCheck("SetDiff");
        
      Spec.ForAny((xs : list[int], ys : list[int]) => xs.SetDiffBy(ys, x => -x).SequenceEqual(xs.Except(ys, SelectEqComparer(x => -x))))
        .QuickCheck("SetDiffBy");
        
      //Spec.ForAny(x : string => Arrays.Single(x).SequenceEqual(array[x]))
      //  .QuickCheck("Single");
        
      Spec.ForAny(xs : list[int] => xs.Sort().SequenceEqual(xs.OrderBy(x => x)))
        .QuickCheck("Sort");
        
      Spec.ForAny(xs : list[int] => xs.Sort(Comparer.Default.Compare).SequenceEqual(xs.OrderBy(x => x, Comparer.Default)))
        .QuickCheck("Sort2");
        
      Spec.ForAny(xs : list[int] => xs.SortBy(x => -x).SequenceEqual(xs.OrderBy(x => -x)))
        .QuickCheck("SortBy");
        
      Spec.ForAny(xs : list[int] => xs.Sort(Comparer.Default).SequenceEqual(xs.OrderBy(x => x, Comparer.Default)))
        .QuickCheck("Sort3");
       
      Spec.ForAny((xs : list[int], x : int) => {
          def (ys, zs) = xs.Span(_ > x);
          def (ys', zs') = xs.ToArray().Span(_ > x);
          ys.SequenceEqual(ys') && zs.SequenceEqual(zs')
        })
        .QuickCheck("Span");
        
      Spec.ForAny((xs : list[int], i : int) => {
          def (ys, zs) = xs.SplitAt(i); 
          def (ys', zs') = xs.ToArray().SplitAt(i);
          ys.SequenceEqual(ys') && zs.SequenceEqual(zs')
        })
        .QuickCheck("SplitAt");
        
      Spec.ForAny((xs : list[int], i : int) => {
          def (ys, zs) = xs.SplitExactlyAt(i); 
          def (ys', zs') = xs.ToArray().SplitExactlyAt(i);
          ys.SequenceEqual(ys') && zs.SequenceEqual(zs')
        })
        .When((xs, i) => i >= 0 && i <= xs.Length)
        .QuickCheck("SplitExactlyAt");
        
      //Spec.ForAny(xs : array[int] => xs.Tail().SequenceEqual(xs.Skip(1)))
      //  .When(xs => xs.Length > 0)
      //  .QuickCheck("Tail");
        
      Spec.ForAny(xs : list[int] => xs.Tails().ZipWithSeq(xs.ToArray().Tails(), Enumerable.SequenceEqual).AndSeq())
        .QuickCheck("Tails");
        
      Spec.ForAny((xs : list[int], n : int) => xs.Get(n).SequenceEqual(xs.Take(n)))
        .QuickCheck("TakeAtMost");
        
      Spec.ForAny((xs : list[int], n : int) => xs.GetExactly(n).SequenceEqual(xs.Take(n)))
        .When((xs, n) => xs.Length >= n && n >= 0)
        .QuickCheck("TakeExactly");
        
      //Spec.ForAny(xs : array[int] => xs.ToNList().SequenceEqual(xs))
      //  .QuickCheck("ToNList");
        
      Spec.ForAny(xs : list[int] => xs.ToSeq().SequenceEqual(xs) && !(xs.ToSeq() is list[int]))
        .QuickCheck("ToSeq");
        
      //Spec.ForAny((xs : list[int], ys : list[int]) => NLists.Replicate(xs, ys.Length).Transpose().Transpose()
      //  .ZipMap(NLists.Replicate(xs, ys.Length), Enumerable.SequenceEqual).AndSeq())
      //  .QuickCheck("Transpose");
        
      Spec.ForAny(xs : list[int] => NLists.Unfold(xs.ToSeq().GetEnumerator(), s => if (s.MoveNext()) Some((s, s.Current)) else None())
        .SequenceEqual(xs))
        .QuickCheck("Arrays.Unfold_1");
        
      Spec.ForAny(n : int => NLists.Unfold(0, s => if (s <= n) Some((s + 1, s)) else None()).SequenceEqual($[0 .. n]))
        .QuickCheck("Arrays.Unfold_2");
        
      Spec.ForAny(xs : list[int] => NLists.UnfoldRight(xs.ToNList().Rev(), s => if (!s.IsEmpty) Some((s.Tail, s.Head)) else None())
        .SequenceEqual(xs))
        .QuickCheck("Arrays.UnfoldRight_1");
        
      Spec.ForAny(n : int => NLists.UnfoldRight(0, s => if (s <= n) Some((s + 1, s)) else None()).SequenceEqual($[n, n - 1 .. 0]))
        .QuickCheck("Arrays.UnfoldRight_2");
        
      Spec.ForAny(zs : list[int*string] => {
        def (xs, ys) = zs.UnZip();
        xs.SequenceEqual(zs.ToArray().Firsts()) && ys.SequenceEqual(zs.ToArray().Seconds())
      }).QuickCheck("UnZip");
      
      Spec.ForAny((xs : list[int], ys : list[string]) => NLists.Zip(xs, ys)
          .SequenceEqual(xs.ToArray().Zip(ys.ToArray())))
        .QuickCheck("Zip");
        
      Spec.ForAny((xs : list[int], ys : list[string]) => xs.ZipWith(ys, zipf)
          .SequenceEqual(xs.ToArray().ZipWith(ys.ToArray(), zipf)))
        .QuickCheck("ZipWith");
    }
  }
  
  public module LimSeq
  {
    public Array01[T]() : Gen[array[T]]
    {
      Any.OfType.[T]().SelectMany(x => Any.WeighedValueIn(WeightAndValue(10, array[x]), WeightAndValue(1, array(0))))
    }
    
    public Array0[T]() : Gen[array[T]]
    {
      Any.Value(array(0))
    }
    
    public NList01[T]() : Gen[list[T]]
    {
      Any.OfType.[T]().SelectMany(x => Any.WeighedValueIn(WeightAndValue(10, [x]), WeightAndValue(1, [])))
    }
    
    public NList0[T]() : Gen[list[T]]
    {
      Any.Value([])
    }
  }
  
  public class ArbitraryList[T] : Arbitrary[list[T]]
  {
    public override  Generator : Gen[list[T]]
    {
      get { Any.OfType.[array[T]]().Select(xs => $[x | x in xs]) }
    }
  }
  
  public class ArbitraryOption[T] : Arbitrary[option[T]]
  {
    public override Generator : Gen[option[T]]
    {
      get { Any.OfType.[T]().SelectMany(x => Any.WeighedValueIn(WeightAndValue(10, Some(x)), WeightAndValue(1, None())))}
    }
  }
  
  public class ArbitraryPair[T, U] : Arbitrary[T*U]
  {
    public override Generator : Gen[T*U]
    {
      get 
      { 
        Any.OfType.[T]().SelectMany(
          x => Any.OfType.[U]().SelectMany(
            y => Any.Value((x,y))))
      }
    }
  }
  
  public class ArbitraryTriple[T, U, V] : Arbitrary[T*U*V]
  {
    public override Generator : Gen[T*U*V]
    {
      get 
      { 
        Any.OfType.[T]().SelectMany(
          x => Any.OfType.[U]().SelectMany(
            y => Any.OfType.[V]().SelectMany(
              z => Any.Value((x, y, z)))))
      }
    }
  }

  public class NArbitraries
  {
      public static List[T]() : Arbitrary[list[T]] { ArbitraryList(); }
      public static Option[T]() : Arbitrary[option[T]] { ArbitraryOption(); }
      public static Pair[T, U]() : Arbitrary[T*U] { ArbitraryPair(); }
      public static Triple[T, U, V]() : Arbitrary[T*U*V] { ArbitraryTriple(); }
  }

}
